package com.demo.task.socket

import android.util.Log
import com.demo.task.ext.getMemoryInfo
import com.demo.task.ext.getSerial
import com.demo.task.ext.getStorageInfo
import com.demo.task.ext.getVersion
import com.demo.task.ext.launchIO
import com.demo.task.ext.toJson
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import io.socket.client.IO
import io.socket.client.IO.socket
import io.socket.client.Socket
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

/**
 *@Author: yyk
 *@CreateTime: 2024-09-14
 *@Description:
 */
class SocketIO private constructor(
    private val builder: Builder
) : RemoteInterface {
    val options = IO.Options().apply {
        reconnection = false
    }
    private val socket by lazy(LazyThreadSafetyMode.NONE) {
        socket(builder.url, options)
    }

    private val _msg = MutableSharedFlow<String>()
    override val msg: SharedFlow<String> get() = _msg

    private val _isConnect = MutableStateFlow(false)
    override val isConnect: StateFlow<Boolean> get() = _isConnect

    private val msgFlow = MutableStateFlow("")

    private var reconnectTimes = 0

    private val job: Job by lazy {
        CoroutineScope(Dispatchers.Default).launch(start = CoroutineStart.LAZY) {
            launch {
                while (true) {
                    delay(55_000)
                    sendHeartbeat()
                }
            }
            launch {
                msgFlow.collectLatest {
                    Log.i(TAG, "msgFlow: $it")
                    if (it.isNotEmpty()) {
                        send(it)
                    }
                }
            }
        }
    }

    private val reconnect: Job by lazy {
        CoroutineScope(Dispatchers.Default).launch(start = CoroutineStart.LAZY) {
            var time = reconnectTimes
            while (true) {
                delay(20_000)
                if (time == reconnectTimes) {
                    socket.disconnect()
                    delay(3_000)
                    socket.connect()
                }
                time = reconnectTimes
            }
        }
    }

    init {
        socket.on(Socket.EVENT_CONNECT) {
            Log.e(TAG, "EVENT_CONNECT: ${it.joinToString()}")
            Logger.i("EVENT_CONNECT: ${it.joinToString()}")
            socket.emit("login", getSerial())
            sendVersion()
            runBlocking { _isConnect.emit(true) }
            job.start()
        }.on(Socket.EVENT_DISCONNECT) {
            Log.e(TAG, "EVENT_DISCONNECT: ${it.joinToString()}")
            Logger.i("EVENT_DISCONNECT: ${it.joinToString()}")
            runBlocking { _isConnect.emit(false) }
        }.on(Socket.EVENT_CONNECT_ERROR) {
            Log.e(TAG, "EVENT_CONNECT_ERROR: ${it.joinToString()}")
            Logger.i("EVENT_CONNECT_ERROR: ${it.joinToString()}")
        }.on(Socket.EVENT_CONNECT_TIMEOUT) {
            Log.e(TAG, "EVENT_CONNECT_TIMEOUT: ${it.joinToString()}")
            Logger.i("EVENT_CONNECT_TIMEOUT: ${it.joinToString()}")
        }.on(Socket.EVENT_MESSAGE) {
            Log.e(TAG, "EVENT_MESSAGE: ${it.joinToString()}")
            Logger.i("EVENT_MESSAGE: ${it.joinToString()}")
        }.on(SOCKET_MSG_EVENT) {
            val msg = it.joinToString()
            Log.i(TAG, "SOCKET_MSG_EVENT: msg:${msg}")
            Logger.i("SOCKET_MSG_EVENT: msg:${msg}")
            runBlocking { _msg.emit(msg) }
            callbackMessage(msg)
        }
    }

    private fun callbackMessage(msg: String) {
        runBlocking {
            launchIO {
                if (msg.contains("time")) {
                    val map = Gson().fromJson(msg, Map::class.java) as MutableMap<Any, Any>
                    val params = mutableMapOf<String, String>()
                    params.putAll(getStorageInfo())
                    params.putAll(getMemoryInfo())
                    map["mahine"] = params
                    send(map.toJson())
                    send(mapOf("sn" to getSerial()).toJson())
                    reconnectTimes += 1
                } else {
                    send(msg)
                }
            }
        }
    }

    override fun connect() {
        socket.connect()
        reconnect.start()
    }

    fun sendSingle(msg: String) {
        runBlocking { msgFlow.emit(msg) }
    }

    override fun send(msg: String) {
        Log.i(TAG, "send: $msg")
        socket.send(msg)
    }

    override fun sendBasic(map: Map<String, Any>) {
        val message = mapOf(
            "type" to "basic",
            "message" to map
        )
        send(Gson().toJson(message))
    }

    override fun close() {
        socket.off()
        job.cancel()
        reconnect.cancel()
    }

    private fun sendVersion() {
        val message = mapOf(
            "type" to "basic",
            "message" to mapOf("androidv" to getVersion())
        )
        send(Gson().toJson(message))
    }

    private fun sendHeartbeat() {
        val message = mapOf(
            "type" to "ping",
            "message" to "online"
        )
        send(Gson().toJson(message))
    }


    class Builder {
        internal var url: String? = null

        fun url(url: String) = apply {
            this.url = url
        }

        fun build(): SocketIO {
            requireNotNull(this.url) { "URL must be set" }
            return SocketIO(this)
        }
    }

    companion object {
        private const val TAG = "SocketIO"
        private const val SOCKET_MSG_EVENT = "new_msg"
    }
}